package io.lvdaxian.mybatis08.datasource.pooled;

import cn.hutool.core.util.StrUtil;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.SQLException;

/**
 * 对 池化连接进行代理，允许我们自己手动关闭连接
 *
 * @author lihh
 */
public class PooledConnection implements InvocationHandler {
  
  private static final String CLOSE = "close";
  private static final Class<?>[] IFACES = new Class<?>[]{Connection.class};
  
  private int hashCode = 0;
  private final PooledDataSource dataSource;
  
  // 真实的连接
  private final Connection realConnection;
  // 代理的连接
  private final Connection proxyConnection;
  
  private long checkoutTimestamp;
  private long createdTimestamp;
  private long lastUsedTimestamp;
  private int connectionTypeCode;
  private boolean valid;
  
  public PooledConnection(Connection connection, PooledDataSource dataSource) {
    this.hashCode = connection.hashCode();
    // 这是 真实的 connection 连接
    this.realConnection = connection;
    this.dataSource = dataSource;
    
    this.createdTimestamp = System.currentTimeMillis();
    this.lastUsedTimestamp = System.currentTimeMillis();
    // 默认的状态 是有效的
    this.valid = true;
    
    // 代理连接
    this.proxyConnection = (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(), IFACES, this);
  }
  
  /**
   * check connection 是否有效
   *
   * @throws SQLException 异常
   * @author lihh
   */
  private void checkConnection() throws SQLException {
    if (!valid)
      throw new SQLException("Error accessing PooledConnection. Connection is invalid.");
  }
  
  /**
   * 尝试设置 连接无效
   *
   * @author lihh
   */
  public void invalidate() {
    valid = false;
  }
  
  
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    String methodName = method.getName();
    
    // 判断是否调用的关闭连接的方法
    if (CLOSE.hashCode() == methodName.hashCode() && StrUtil.equals(CLOSE, methodName)) {
      // todo 这里为啥不直接关闭呢，有可能连接池此时有空闲的位置，所以不能直接效果 要添加到连接池中
      dataSource.pushConnection(this);
      return null;
    } else {
      if (!Object.class.equals(method.getDeclaringClass())) {
        // 除了toString()方法，其他方法调用之前要检查connection是否还是合法的,不合法要抛出SQLException
        checkConnection();
      }
    }
    // 其他的就正常执行
    return method.invoke(realConnection, args);
  }
  
  /**
   * 这里 也判断连接是否有效
   *
   * @return 返回连接有效状态
   * @author lihh
   */
  public boolean isValid() {
    // 如果连接是真实存在的 && 别忘记 ping 了
    return valid && realConnection != null && dataSource.pingConnection(this);
  }
  
  public Connection getRealConnection() {
    return realConnection;
  }
  
  public Connection getProxyConnection() {
    return proxyConnection;
  }
  
  public int getRealHashCode() {
    return realConnection == null ? 0 : realConnection.hashCode();
  }
  
  public int getConnectionTypeCode() {
    return connectionTypeCode;
  }
  
  public void setConnectionTypeCode(int connectionTypeCode) {
    this.connectionTypeCode = connectionTypeCode;
  }
  
  public long getCreatedTimestamp() {
    return createdTimestamp;
  }
  
  public void setCreatedTimestamp(long createdTimestamp) {
    this.createdTimestamp = createdTimestamp;
  }
  
  public long getLastUsedTimestamp() {
    return lastUsedTimestamp;
  }
  
  public void setLastUsedTimestamp(long lastUsedTimestamp) {
    this.lastUsedTimestamp = lastUsedTimestamp;
  }
  
  public long getTimeElapsedSinceLastUse() {
    return System.currentTimeMillis() - lastUsedTimestamp;
  }
  
  public long getAge() {
    return System.currentTimeMillis() - createdTimestamp;
  }
  
  public long getCheckoutTimestamp() {
    return checkoutTimestamp;
  }
  
  public void setCheckoutTimestamp(long timestamp) {
    this.checkoutTimestamp = timestamp;
  }
  
  public long getCheckoutTime() {
    return System.currentTimeMillis() - checkoutTimestamp;
  }
  
  @Override
  public int hashCode() {
    return hashCode;
  }
  
  @Override
  public boolean equals(Object obj) {
    if (obj instanceof PooledConnection) {
      return realConnection.hashCode() == (((PooledConnection) obj).realConnection.hashCode());
    } else if (obj instanceof Connection) {
      return hashCode == obj.hashCode();
    } else {
      return false;
    }
  }
}
